﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Socium.Domain;
using Socium.Domain.Collections;
using Socium.Domain.Persons;
using Socium.Domain.Documents;
using Socium.Domain.Tests;
using Socium.XmlTypes.Types;
using Person = Socium.Domain.Documents.Person;

namespace XmlConverter {
	class Parser {
		readonly Database _database = new Database();
		//readonly IDictionary<long, Person> _persons = new Dictionary<long, Person>();

		readonly ICollection<KeyValuePair<string, Document>> _documents = new Collection<KeyValuePair<string, Document>>();

		public Parser(string path) {
			var element = Parse(path);

			var data = new RootElementType();
			var mismatches = data.Parse(element);

			if (mismatches.Count > 0) {
				throw new ArgumentException(string.Join("\n", mismatches.Select(m => m.ToString())));
			}

			var persons = new Registry<long, Socium.Domain.Persons.Person>(data.Person.MapLazy(GetPerson));

			_database.Persons.AddRange(persons);
			_database.Tests.AddRange(data.Test.Select(GetTest));

			_documents.AddRange(data.Card.MapLazy(c => GetDocument(c, persons)));
		}

		static KeyValuePair<string, Document> GetDocument(CardType entry, Registry<long, Socium.Domain.Persons.Person> persons) {
			var criterias = entry.Criteria.Map(GetDocumentCriteria);
			var document_persons = entry.CardPerson.Map(p => GetDocumentPerson(p, persons));

			var document = new Document();
			document.Criterias.AddRange(criterias.MapLazy(pair => pair.Value));
			document.Persons.AddRange(document_persons.MapLazy(pair => pair.Value));

			var criterias_dictionary = new Dictionary<long, RelationGroup>();
			criterias_dictionary.AddRange(criterias);

			var persons_dictionary = new Dictionary<long, Person>();
			persons_dictionary.AddRange(document_persons);

			document.Relations.AddRange(entry.Relation.MapLazy(r => GetDocumentRelation(r, criterias_dictionary, persons_dictionary)));

			var sociogram = GetSociogram(entry, persons_dictionary);
			document.Sociograms.Add(sociogram);

			var sociomatrix = new Sociomatrix {Name = entry.Name};
			sociomatrix.Persons.AddRange(document_persons.MapLazy(pair => pair.Value));
			document.Sociomatrices.Add(sociomatrix);

			return new KeyValuePair<string, Document>(entry.Name, document);
		}

		static KeyValuePair<long, RelationGroup> GetDocumentCriteria(CriteriaType entry) {
			var color = entry.Color.HasValue ? entry.Color.Value : 0;
			var criteria = new RelationGroup {Name = entry.Name, Color = color};
			return new KeyValuePair<long, RelationGroup>(entry.Id, criteria);
		}

		static Relation GetDocumentRelation(RelationType entry, IDictionary<long, RelationGroup> criterias, IDictionary<long, Person> persons) {
			var @from = persons[entry.Elector];
			var to = persons[entry.Elected];
			var criteria = criterias[entry.Criteria];
			return new Relation(criteria, @from, to);
		} 

		static KeyValuePair<long, Person> GetDocumentPerson(CardPersonType entry, Registry<long, Socium.Domain.Persons.Person> persons) {
			var key = entry.Id;
			var person = persons[key];
			var document_person = new Person(person);
			return new KeyValuePair<long, Person>(key, document_person);
		}

		static Sociogram GetSociogram(CardType entry, IDictionary<long, Person> persons) {
			var sociogram = new Sociogram {Name = entry.Name};
			sociogram.Nodes.AddRange(entry.CardPerson.MapLazy(p => GetNode(p, persons)));

			return sociogram;
		}

		static Node GetNode(CardPersonType entry, IDictionary<long, Person> persons) {
			var person = persons[entry.Id];
			return new Node(person) { Left = entry.X, Top = entry.Y };
		}


		public void Save(string path) {
			_database.Save(Path.Combine(path, "Socium.sdb"));
			_documents.Iterate(pair => pair.Value.Save(Path.Combine(path, pair.Key + ".socium")));
		}

		static XElement Parse(string path) {
			using (var stream = File.OpenRead(path)) {
				return XElement.Load(stream);
			}
		}

		static KeyValuePair<long, Socium.Domain.Persons.Person> GetPerson(PersonType entry) {
			var person = new Socium.Domain.Persons.Person {
				GivenName = entry.GivenName,
				FamilyName = entry.FamilyName,
				Patronymic = entry.Patronymic,
				Gender = Gender.Male,
			};

			return new KeyValuePair<long, Socium.Domain.Persons.Person>(entry.Id, person);
		}

		static Test GetTest(TestType entry) {
			var test = new Test(entry.Name);
			test.Questions.AddRange(entry.Question.Select(GetQuestion));

			return test;
		}

		static Question GetQuestion(QuestionType entry) {
			var text = entry.Text;

			// ReSharper disable PossibleInvalidOperationException
			switch (entry.Type) {
				case "Closed":
					var closed_question = new ClosedQuestion(text, new CountRange(entry.MinCount.Value, entry.MaxCount.Value));

					closed_question.Variants.AddRange(entry.Variant.Select(GetVariant));

					return closed_question;
				case "Election":
					return new ElectionQuestion(text, entry.MaxElections.Value);
				default:
					throw new InvalidOperationException();
			}

			// ReSharper restore PossibleInvalidOperationException
		}

		static Variant GetVariant(VariantType entry) {
			return new Variant(entry.Text);
		}
	}
}
